package com.ysz.component.page;

import com.ysz.component.constant.MyBatisConst;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.sql.SQLException;
import java.util.*;

/**
 * @ClassName MyBatisInterceptor
 * @Description mybatis自定义拦截器
 * @Data 9:47
 * @Version 1.0
 * @Author ysz
 */
@Intercepts({
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class MyBatisPageInterceptor implements Interceptor {

    /**
     * 平台类型 mysql oracle
     */
    private String PLAT_FORM_TYPE;
    /**
     * 分页标志
     */
    private String BY_PAGE_FLAG;
    /**
     * 构建自定义count查询
     */
    private String COUNT_SUFFIX;
    /**
     * 构建默认count查询
     */
    private String AUTO_COUNT_SUFFIX = "auto_count_suffix";
    /**
     * 构建空的resultmapping
     */
    private List<ResultMapping> EMPTY_RESULT_MAPPING = new ArrayList<>();
    private ObjectFactory OBJECT_FACTORY = new DefaultObjectFactory();
    private ObjectWrapperFactory OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
    private ReflectorFactory REFLECTOR_FACTORY = new DefaultReflectorFactory();


    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        try {
            //获取被拦截的方法名  查看被拦截的方法名是不是一个需要分页的方法
            Object[] args = invocation.getArgs();
            MappedStatement mappedStatement = (MappedStatement) args[0];
            //只匹配分页规则则进行分页
            if (mappedStatement.getId().matches(BY_PAGE_FLAG) && !mappedStatement.getId().contains(COUNT_SUFFIX)) {
                //获取方法上的参数
                PageView pageView = getPageViewFromParameter(args[1]);
                //获取sql
                BoundSql boundSql = mappedStatement.getBoundSql(args[1]);
                String sql = boundSql.getSql();
                Executor target = (Executor) invocation.getTarget();
                ResultHandler resultHandler = (ResultHandler) args[3];
                Long count = null;
                if (Objects.nonNull(pageView.getDoCount()) && pageView.getDoCount()) {
                    count = getCount(target, mappedStatement, args[1], resultHandler);
                }
                if ((Objects.nonNull(pageView.getDoCount())
                        && pageView.getDoCount())
                        && (Objects.isNull(count) || count <= 0)) {
                    pageView.setTotal(0L);
                    pageView.setData(new ArrayList());
                    return invocation.proceed();
                } else {
                    pageView.setTotal(count);
                    //执行分页
                    String newPageSql = buildPageSql(pageView, sql);
                    //将分页的sql反射回BoundSql中去
                    resetSql2Invocation(invocation, newPageSql, mappedStatement, boundSql);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return invocation.proceed();
    }


    public PageView getPageViewFromParameter(Object parameterObject) {
        if (parameterObject instanceof PageView) {
            return (PageView) parameterObject;
        } else if (parameterObject instanceof HashMap) {
            for (Map.Entry entry : (Set<Map.Entry>) ((Map) parameterObject).entrySet()) {
                if (entry.getValue() instanceof PageView) {
                    return (PageView) entry.getValue();
                }
            }
        }
        return new PageView();
    }


    private void resetSql2Invocation(Invocation invocation, String newPageSql, MappedStatement mappedStatement, BoundSql boundSql) {
        MappedStatement newMappedStatement = generalPageMappedStatement(mappedStatement, new BoundSqlSqlSource(boundSql));
        MetaObject metaObject = MetaObject.forObject(newMappedStatement, OBJECT_FACTORY, OBJECT_WRAPPER_FACTORY, REFLECTOR_FACTORY);
        metaObject.setValue("sqlSource.boundSql.sql", newPageSql);
        invocation.getArgs()[0] = newMappedStatement;
    }


    /**
     * 构建分页的mappedStatement
     *
     * @author ysz
     * @date 2022/2/8 17:38
     */
    private MappedStatement generalPageMappedStatement(MappedStatement mappedStatement, BoundSqlSqlSource boundSqlSqlSource) {
        MappedStatement.Builder builder = new MappedStatement.Builder(mappedStatement.getConfiguration(), mappedStatement.getId(), boundSqlSqlSource, mappedStatement.getSqlCommandType());
        builder.resource(mappedStatement.getResource());
        builder.fetchSize(mappedStatement.getFetchSize());
        builder.statementType(mappedStatement.getStatementType());
        builder.keyGenerator(mappedStatement.getKeyGenerator());
        String[] keyProperties = mappedStatement.getKeyProperties();
        if (Objects.nonNull(keyProperties) && keyProperties.length > 0) {
            StringBuilder sb = new StringBuilder();
            for (String property : keyProperties) {
                sb.append(property).append(",");
            }
            sb.deleteCharAt(sb.length() - 1);
            builder.keyProperty(sb.toString());
        }
        builder.timeout(mappedStatement.getTimeout());
        builder.parameterMap(mappedStatement.getParameterMap());
        builder.resultMaps(mappedStatement.getResultMaps());
        builder.resultSetType(mappedStatement.getResultSetType());
        builder.cache(mappedStatement.getCache());
        builder.flushCacheRequired(mappedStatement.isFlushCacheRequired());
        builder.useCache(mappedStatement.isUseCache());
        return builder.build();
    }


    private class BoundSqlSqlSource implements SqlSource {
        private BoundSql boundSql;

        BoundSqlSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
        }

        @Override
        public BoundSql getBoundSql(Object parameterObject) {
            return boundSql;
        }
    }


    /**
     * 构建一个带分页参数的sql
     *
     * @author ysz
     * @date 2022/2/8 17:19
     */
    private String buildPageSql(PageView pageView, String sql) {
        if (Objects.equals(PLAT_FORM_TYPE, MyBatisConst.mysql_type.getValue())) {
            return buildPageSqlByMySql(pageView, sql);
        } else if (Objects.equals(PLAT_FORM_TYPE, MyBatisConst.oracle_type.getValue())) {
            return buildPageSqlByOracle(pageView, sql);
        }
        throw new RuntimeException("暂时不支持的数据库类型");
    }


    /**
     * 构建oracle的分页语句
     *
     * @author ysz
     * @date 2022/2/8 17:28
     */
    private String buildPageSqlByOracle(PageView pageView, String sql) {
        throw new RuntimeException("暂时不支持ORACLE");
    }


    /**
     * 构建mysql的分页语句
     *
     * @author ysz
     * @date 2022/2/8 17:27
     */
    private String buildPageSqlByMySql(PageView pageView, String sql) {
        return sql + " limit " + (pageView.getPage() * (pageView.getPage() - 1)) + "," + pageView.getLimit();
    }

    /**
     * 对sql进行封装执行count查询
     *
     * @author ysz
     * @date 2022/2/8 16:06
     */
    private Long getCount(Executor target, MappedStatement mappedStatement, Object parameterObject, ResultHandler resultHandler) throws SQLException {
        BoundSql boundSql = mappedStatement.getBoundSql(parameterObject);
        //需要生成count查询
        if (StringUtils.isEmpty(COUNT_SUFFIX)) {
            return executeAutoTotalCount(boundSql, mappedStatement, target, parameterObject, resultHandler);
        } else {
            //不需要生成count查询使用用户自己写的
            //查询用户的count查询对应的方法
            String manualCountMappedStatementId = mappedStatement.getId() + COUNT_SUFFIX;
            MappedStatement manualCountMappedStatement = null;
            try {
                manualCountMappedStatement = mappedStatement.getConfiguration().getMappedStatement(manualCountMappedStatementId);
            } catch (Exception e) {
                //找不到自定义的count查询会报错  忽略 ignore
            }
            if (Objects.isNull(manualCountMappedStatement)) {
                //未找到 指定自动生成count
                return executeAutoTotalCount(boundSql, mappedStatement, target, parameterObject, resultHandler);
            } else {
                boundSql = manualCountMappedStatement.getBoundSql(parameterObject);
                return executeManualTotalCount(boundSql, manualCountMappedStatement, target, parameterObject, resultHandler);
            }
        }
    }


    public Long executeManualTotalCount(BoundSql boundSql, MappedStatement mappedStatement, Executor target, Object parameterObject, ResultHandler resultHandler) throws SQLException {
        //创建count的查询缓存
        CacheKey cacheKey = target.createCacheKey(mappedStatement, parameterObject, RowBounds.DEFAULT, boundSql);
        BoundSql countBoundSql = new BoundSql(mappedStatement.getConfiguration(), boundSql.getSql(), boundSql.getParameterMappings(), parameterObject);
        //执行sql
        List<Object> query = target.query(mappedStatement, parameterObject, RowBounds.DEFAULT, resultHandler, cacheKey, countBoundSql);
        return ((Number) query.get(0)).longValue();
    }

    public Long executeAutoTotalCount(BoundSql boundSql, MappedStatement mappedStatement, Executor target, Object parameterObject, ResultHandler resultHandler) throws SQLException {
        String sql = boundSql.getSql();
        sql = "select count(*) from (" + sql + ") tmp_count";
        //构建一个count查询的mappedStatement
        String mappedId = mappedStatement.getId() + AUTO_COUNT_SUFFIX;
        MappedStatement countMappedStatement = generalCountMappedStatement(mappedStatement, mappedId);
        //创建count的查询缓存
        CacheKey cacheKey = target.createCacheKey(countMappedStatement, parameterObject, RowBounds.DEFAULT, boundSql);
        BoundSql countBoundSql = new BoundSql(countMappedStatement.getConfiguration(), sql, boundSql.getParameterMappings(), parameterObject);
        //执行sql
        List<Object> query = target.query(countMappedStatement, parameterObject, RowBounds.DEFAULT, resultHandler, cacheKey, countBoundSql);
        return ((Long) query.get(0));
    }

    /**
     * 构建一个执行count查询的mappedStatement
     *
     * @author ysz
     * @date 2022/2/8 16:12
     */
    private MappedStatement generalCountMappedStatement(MappedStatement mappedStatement, String mappedId) {
        MappedStatement.Builder builder = new MappedStatement.Builder(mappedStatement.getConfiguration(), mappedId, mappedStatement.getSqlSource(), mappedStatement.getSqlCommandType());
        builder.resource(mappedStatement.getResource());
        builder.fetchSize(mappedStatement.getFetchSize());
        builder.statementType(mappedStatement.getStatementType());
        builder.keyGenerator(mappedStatement.getKeyGenerator());
        String[] keyProperties = mappedStatement.getKeyProperties();
        if (Objects.nonNull(keyProperties) && keyProperties.length > 0) {
            StringBuilder sb = new StringBuilder();
            for (String property : keyProperties) {
                sb.append(property).append(",");
            }
            sb.deleteCharAt(sb.length() - 1);
            builder.keyProperty(sb.toString());
        }
        builder.timeout(mappedStatement.getTimeout());
        builder.parameterMap(mappedStatement.getParameterMap());


        builder.resultMaps(new ArrayList<ResultMap>() {{
            add(new ResultMap.Builder(mappedStatement.getConfiguration(), mappedStatement.getId(), Long.class, EMPTY_RESULT_MAPPING).build());
        }});

        builder.resultSetType(mappedStatement.getResultSetType());
        builder.cache(mappedStatement.getCache());
        builder.flushCacheRequired(mappedStatement.isFlushCacheRequired());
        builder.useCache(mappedStatement.isUseCache());
        return builder.build();
    }

    @Override
    public Object plugin(Object target) {
        return target instanceof Executor ? Plugin.wrap(target, this) : target;
    }

    @Override
    public void setProperties(Properties properties) {
        PLAT_FORM_TYPE = (String) properties.get(MyBatisConst.database_type.getKey());
        BY_PAGE_FLAG = (String) properties.get(MyBatisConst.page_flag.getKey());
        COUNT_SUFFIX = (String) properties.get(MyBatisConst.count_flag.getKey());
    }
}

